package client.comms;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.protocol.HttpClientContext;
import client.view.DownloadResultsPanel;
import client.view.ReportsPanel;
import client.view.SettingsPanel;
import client.view.UploadPanel;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.*;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.*;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.protocol.BasicHttpContext;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.mail.internet.ContentType;
import javax.swing.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.net.*;
import java.net.CookieStore;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

// December 2013 new imports due to deprecated API's
import org.apache.http.client.protocol.HttpClientContext;


/**
 * Created by IntelliJ IDEA.
 * User: sam
 * Date: 16/02/12
 * Time: 06:23
 * To change this template use File | Settings | File Templates.
 */
public class PostData implements PropertyChangeListener {

    //String mfResource = "http://localhost:8080/musicfinder/MFHttpServlet";//http://musicalms.samwootton.com";

    String mfResource = "http://samwootton.com:8080/mfservice/music";

    Writer wr;
    FileWriter fstream;
    URL url;
    Document doc = null;
    HttpURLConnection conn = null;
    DownloadTask downloadTask;
    UncompressTask uncompressTask;
    DownloadResultsPanel resultsPanel;
    UploadPanel uploadPanel;

    int taskState;
    final int DOWNLOAD = 1;
    final int UNCOMPRESSING = 2;
    final int UPLOADING = 3;
    final int COMPLETE = 4;
    final int ERROR = 5;

    InputStream inputStream = null;
    InputStreamReader inputStreamReader = null;
    File outFile = null;
    FileOutputStream fileOutputStream = null;
    ZipFile zipFile = null;
    ZipInputStream zipInputStream = null;
    FileOutputStream out = null;
    InputStream zipEntryInputStream;
    int outFileSize = 0;
    String downloadDirName = "";
    double contentLength;
    int totalCompressedSize = 0;
    int totalUncompressedSize = 0;

    static String JSESSIONID_COOKIE_VALUE;
    static String JSESSIONID_COOKIE_PATH;

    public PostData() {
    }

    public Document sendDataForWriteReponse(String postData) {


        ReportsPanel.log("sendDataForWriteReponse "+postData);

        try {
            url = new URL(mfResource);

            //if(conn == null ){
                conn = (HttpURLConnection) url.openConnection(); // open connection, same as connection.connect()
            //}


            conn.setDoInput(true);   // specify that we will send output
            conn.setDoOutput(true);  // and accept input
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "text/xml");  // tell the web server what we are sending
            conn.setUseCaches(false);
            conn.setConnectTimeout(20000);  // long timeout, but not infinite
            conn.setReadTimeout(20000);

            // required for tomcat access http://musicalms.samwootton.com/
            String basic_auth = new String(Base64.encodeBase64(("bruno" + ":" + "bear").getBytes()));
            conn.setRequestProperty("Authorization", "Basic " + basic_auth);

            //String encoding = new sun.misc.BASE64Encoder().encode(userPassword.getBytes());
            //conn.setRequestProperty("Authorization", "Basic " + encoding);
            //System.out.println("Authorization " + "Basic " + encoding);

            try {
                //fstream = new FileWriter("mf_out");
                //BufferedWriter out = new BufferedWriter(fstream);
                //out.write(postData);
                //out.close();

                if(PostData.JSESSIONID_COOKIE_VALUE != null) {

                    ReportsPanel.log("*** Setting Request cookie "+PostData.JSESSIONID_COOKIE_VALUE);   // JSESSIONID=0AEA6305720CEC98D466849FAA4FBAC4; Path=/musicfinder

                    //ReportsPanel.log("JSESSIONID="+JSESSIONID_COOKIE_VALUE +" Path="+JSESSIONID_COOKIE_PATH);
                    // JSESSIONID=B5F9DB26DB30EE0EF768BEED03F947CA Path=/musicfinder
                    // Cannot use "Set-Cookie" that server uses.  Doesnt count as a real cookie, so use "Cookie" instead.

                    conn.setRequestProperty("Cookie", "JSESSIONID="+JSESSIONID_COOKIE_VALUE +"; Path="+JSESSIONID_COOKIE_PATH);
                }

                wr = new OutputStreamWriter(conn.getOutputStream());

                wr.write(postData);
                wr.flush();
                wr.close();
            }catch (Exception e) {
                System.err.println("Error: " + e.getMessage());
            }

            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();

            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            ReportsPanel.log("conn "+conn);
            Map map = conn.getHeaderFields();
            Set set = map.entrySet();
            Iterator iterator = set.iterator();

            /**
             * getHeaderFields null=[HTTP/1.1 200 OK]
getHeaderFields Date=[Sat, 02 Jun 2012 12:59:26 GMT]
getHeaderFields Content-Length=[171]
getHeaderFields Set-Cookie=[JSESSIONID=AD6DCE9568F41E787E1217A7AF026090; Path=/musicfinder]
getHeaderFields Content-Type=[application/xhtml+xml]
getHeaderFields Server=[Apache-Coyote/1.1]
             */

            while (iterator.hasNext()) {
                ReportsPanel.log("writeResponse.getHeaderFields "+iterator.next());
            }

            String wholeCookie = conn.getHeaderField("Set-Cookie");

            if(wholeCookie != null) {

                PostData.JSESSIONID_COOKIE_VALUE = wholeCookie.split(";")[0].split("JSESSIONID=")[1];

                PostData.JSESSIONID_COOKIE_PATH = wholeCookie.split(";")[1].split("Path=")[1];

                ReportsPanel.log("*** Response PostData.JSESSIONID_COOKIE_VALUE "+PostData.JSESSIONID_COOKIE_VALUE);   // JSESSIONID=0AEA6305720CEC98D466849FAA4FBAC4; Path=/musicfinder
                ReportsPanel.log("*** Response PostData.JSESSIONID_COOKIE_PATH "+PostData.JSESSIONID_COOKIE_PATH);

                //conn.setRequestProperty("Set-Cookie", PostData.JSESSIONID_COOKIE);
            }

            if (conn.getContentLength() != -1) {

                InputStream inputStream = conn.getInputStream();
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream);

                doc = dBuilder.parse(new InputSource(inputStreamReader));

            } else {
                ReportsPanel.log("Server Error");
            }

            conn.disconnect();

            System.runFinalization();
            System.gc();
        } catch (IOException e) {

            e.printStackTrace();
            ReportsPanel.log(e.toString());
        } catch (ParserConfigurationException e) {

            e.printStackTrace();
            ReportsPanel.log(e.toString());
        } catch (SAXException e) {
            // The "premature end of file" type errors from the xerces SAX implementation usually indicates that the
            // SAX parser expected data, but got none (not null, but an initialized empty InputSource).
            e.printStackTrace();
            ReportsPanel.log(e.toString());
        }
        return doc;
    }

    public void sendDataForStreamReponse(String postData, DownloadResultsPanel resultsPanel) {

        this.resultsPanel = resultsPanel;

        ReportsPanel.log("PostData.sendDataForStreamReponse()");

        resultsPanel.setProgressBarStr("Contacting repository...");

        //downloadDirName = "/home/sam/mf_server_dump/music_finder_downloads";
        downloadDirName = SettingsPanel.USER_SETTING_DOWNLOAD_DIR;

        //File downloadDir = new File(downloadDirName);

        //downloadDir.mkdirs();
        ReportsPanel.log("Creating: "+downloadDirName+File.separator+"outfile.zip");

        outFile = new File(downloadDirName+File.separator+"outfile.zip");

        //try {
            //outFile.createNewFile();
        //} catch (IOException e) {
            //e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        //}

        //outFile = outFile.getAbsoluteFile();

        ReportsPanel.log("outFile.getAbsolutePath() "+outFile.getAbsolutePath());
        //ReportsPanel.log("outFile.getAbsolutePath() "+outFile.getAbsolutePath());

        downloadTask = new DownloadTask(postData);
        downloadTask.addPropertyChangeListener(this);
        downloadTask.execute();

        uncompressTask = new UncompressTask(postData);
        uncompressTask.addPropertyChangeListener(this);
    }

    // Responds to changes in the the Task's progress property
    public void propertyChange(PropertyChangeEvent evt) {
        String propChanged = evt.getPropertyName();

        if ("progress" == propChanged) {
            int progress = (Integer) evt.getNewValue();
            resultsPanel.updateProgressBar(progress);
        }
    }



    // TODO set max value, 100% (total file size)

    /**
     * SwingWorker is an abstract class which hides the threading complexities from the developer.
     * It's an excellent candidate for applications that are required to do tasks
     * (such as retrieving information over the network/internet) which may take some time to finish.
     * It's ideal to detach such tasks from the application and simply keep an eye on their progress.
     * But before we hit the road and start working with the swing worker we have to see what "eye"
     * are we going to put on our worker so to say.
     *
     * SwingWorker: an abstract class to perform lengthy GUI-interacting tasks in a dedicated thread.

When writing a multi-threaded application using Swing, there are two constraints to keep in mind: (refer to How to Use Threads for more details):

    Time-consuming tasks should not be run on the Event Dispatch Thread. Otherwise the application becomes unresponsive.
    Swing components should be accessed on the Event Dispatch Thread only.

     */


    public HttpPost httpPost;
    public DefaultHttpClient client;
    public CloseableHttpClient closeableHttpClient;
    public ProgressMultipartEntity multipartEntity;
    public UploadTask uploadTask;
    public long uploadProgress;


    class UploadTask extends SwingWorker<Void, Void> implements ProgressListener{
        //class UploadTask extends Thread implements ProgressListener{

        String postData;
        String uploadFile; // client_uploadfile.zip
        UploadPanel uploadPanel;
        long progress = 0;
        File tmpUploads; // client_uploadfile.zip
        File tmpData;   // client_tmpUploadXMLData.xml
        BasicHttpContext localcontext;

        public UploadTask(String postData, String uploadFile, UploadPanel uploadPanel, File tmpUploads, File tmpData){
            super();
            this.postData = postData;
            this.uploadFile = uploadFile;
            this.uploadPanel = uploadPanel;
            this.tmpUploads = tmpUploads;
            this.tmpData = tmpData;
        }

        // Main task. Executed in background thread.
        //@Override
        public Void doInBackground() {

            ReportsPanel.log("UploadTask.doInBackground()");

            try {

                HttpGet httpGet = new HttpGet("/");
                HttpPost httpPost = new HttpPost(mfResource);

                /*
                if(httpPost == null){
                    httpPost = new HttpPost(mfResource);
                    httpGet = new HttpGet("/");     // rest is not a host but a path (or mapping) within the host.
                }
                */

                // If you have http://musicalms.samwootton.com, you will get an UnknownHostException
                //HttpHost httpHost = new HttpHost("musicalms.samwootton.com", 80, "http");
                HttpHost httpHost = new HttpHost("samwootton.com:8080/mfservice/music", 8080, "http");

                if(client == null) {

                    HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

                    closeableHttpClient = HttpClients.createDefault();
                    //CloseableHttpClient closeableHttpClient = httpClientBuilder.build();
                    //CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpPost);

                    client = new DefaultHttpClient();

                    //client.getParams().setAuthenticationPreemptive(true);
                    //AuthScope authScope = new AuthScope(targetHost.getHostName(), targetHost.getPort(), AuthScope.ANY_REALM);    // AuthScope.ANY_SCHEME // AuthScope.ANY_REALM

                    UsernamePasswordCredentials usernamePasswordCredentials = new UsernamePasswordCredentials("bruno", "bear");

                    client.getCredentialsProvider().setCredentials(AuthScope.ANY, usernamePasswordCredentials);

                    AuthCache authCache = new BasicAuthCache();

                    // Generate BASIC scheme object and add it to the local auth cache

                    BasicScheme basicScheme = new BasicScheme();

                    // pre-populating the cache forces pre-emptive auth
                    // localcontext.setAttribute("preemptive-auth", basicAuth);
                    authCache.put(httpHost, basicScheme);

                    // Add AuthCache to the execution context
                    localcontext = new BasicHttpContext();
                    localcontext.setAttribute("http.auth.auth-cache", authCache);

                    /**
                     * java.net.UnknownHostException: http://musicalms.samwootton.com
                     at java.net.Inet6AddressImpl.lookupAllHostAddr(Native Method)
                     at java.net.InetAddress$1.lookupAllHostAddr(InetAddress.java:849)
                     */

                    // brute-forcing the Basic Authentication header into the request:
                    String basic_auth = new String(Base64.encodeBase64(("bruno" + ":" + "bear").getBytes()));
                    httpPost.addHeader("Authorization", "Basic " + basic_auth);
                }

                //ReportsPanel.log("Setting cookie req for upload "+PostData.JSESSIONID_COOKIE_VALUE);
                //ReportsPanel.log("Setting cookie req for upload "+PostData.JSESSIONID_COOKIE_PATH);

                /**
                 * Start December 2013 deprecation changes
                 * Casting to CookieStore
                 * New add() method with URL
                 */

                HttpCookie cookie = new HttpCookie("JSESSIONID", PostData.JSESSIONID_COOKIE_VALUE);
                //BasicClientCookie cookie = new BasicClientCookie("JSESSIONID", PostData.JSESSIONID_COOKIE_VALUE);
                cookie.setPath(PostData.JSESSIONID_COOKIE_PATH);

                //CookieStore cookieStore = (CookieStore) new BasicCookieStore();

                ReportsPanel.log("==============================================================");
                /*
                //cookie = cookieStore.getCookies().get(0);
                //cookieStore.addCookie(cookie);
                try {
                    cookieStore.add(new URI(mfResource), cookie);
                } catch (URISyntaxException e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                }
                */
                // End December 2013 deprecation changes

                //HttpContext localContext = new BasicHttpContext();
                //localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
                //client.setCookieStore(cookieStore);

                ReportsPanel.log("Setting authentication for upload");
                httpPost.addHeader("Cookie", "JSESSIONID="+JSESSIONID_COOKIE_VALUE +"; Path="+JSESSIONID_COOKIE_PATH);


                /**
                 * Servlet ends up with:
                 *
                 * isMultipartContent true

                 checkUserSession()
                 cookies [Ljavax.servlet.http.Cookie;@70928739
                 cookies [Ljavax.servlet.http.Cookie;@70928739
                 cookieName JSESSIONID
                 cookieValue 7C227350F11C3138453B8F4756F35049
                 checkUserSession() session username Sam
                 <?xml version="1.0" encoding="ISO-8859-1"?>
                 <MFResponse>
                 <Response type='Login'>
                 <Login name='Sam' session='7C227350F11C3138453B8F4756F35049'/></Response>
                 </MFResponse>
                 */

                multipartEntity = new ProgressMultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, (ProgressListener) this);

                FileBody userUploads = new FileBody(tmpUploads); // "application/zip" (client_uploadfile.zip)
                Writer output = new BufferedWriter(new FileWriter(tmpData)); // client_tmpUploadXMLData.xml

                output.write(postData);
                output.close();

                FileBody uploadData = new FileBody(new File(tmpData.getAbsolutePath())); //"text/plain" (client_tmpUploadXMLData.xml)

                multipartEntity.addPart("userUploads",userUploads);
                multipartEntity.addPart("uploadInfo",uploadData);

                httpPost.setEntity(multipartEntity);

                ReportsPanel.log("Contacting Music-alms server");

                CloseableHttpResponse response = closeableHttpClient.execute(httpPost);

                //HttpResponse response = client.execute(httpHost, httpGet, localcontext);

                ReportsPanel.log("response: "+response);

                ReportsPanel.log("HttpResponse "+response.toString());

                String contentType = response.getFirstHeader("Content-Type").getValue();
                String contentLength = response.getFirstHeader("Content-Length").getValue();

                ReportsPanel.log("contentLength: "+contentLength);

                // **********************************************************
                // FOR XML RESPONSE THERE IS A CONTENT TYPE:
                // HttpResponse contentType application/xhtml+xml
                // weirdly, just checking for this seems to stop SAX Parsing
                // error when it is NOT present (i.e. when upload is occurring)
                // but we will use it anyway, see below
                // **********************************************************

                ReportsPanel.log("HttpResponse contentType "+contentType);


                // upload when session is ok
                //HttpResponse HTTP/1.1 200 OK [Server: Apache-Coyote/1.1, Content-Length: 3607, Date: Sat, 02 Jun 2012 21:21:42 GMT]

                // upload when session has expired
                //HttpResponse HTTP/1.1 200 OK [Server: Apache-Coyote/1.1, Content-Type: application/xhtml+xml, Content-Length: 131, Date: Sat, 02 Jun 2012 21:23:46 GMT]

                //HttpResponse response = client.execute(httpPost, localContext);

                // text/plain used for % writing too?  even if we write XML from server, we have already started writing progress
                // so SAXException [Fatal Error] :19:1: Content is not allowed in trailing section.
                // UPDATE: prob only xhtml+xml, because we set it in write() on MFServer.

                // TODO change to MF-Error

                //boolean serverError = Boolean.parseBoolean(conn.getHeaderField("MFServer-Error"));

                if(contentType.equals("application/xhtml+xml") /*|| contentType.equals("text/plain")*/ ) { // this is blank if it is uploading / sending back progress (int, 1-100%)

                    InputStream httpRespIS = response.getEntity().getContent();

                    InputStreamReader inputStreamReader = new InputStreamReader(httpRespIS);

                    DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
                    DocumentBuilder dBuilder = null;

                    try {
                        dBuilder = dbFactory.newDocumentBuilder();
                        doc = dBuilder.parse(new InputSource(inputStreamReader));
                    } catch (ParserConfigurationException e) {
                        ReportsPanel.log(e.toString());
                        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    } catch (SAXException e) {
                        ReportsPanel.log(e.toString());
                        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    }

                    NodeList nList = doc.getElementsByTagName("Response");
                    Node nNode = nList.item(0);
                    Element eElement = (Element) nNode;
                    String type = eElement.getAttribute("type");

                    ReportsPanel.log("Upload response type: "+type);

                    /**
                     * <MFResponse>
                     *     <Response type='Error' code='MF_SESSION_EXPIRED'>
                     *         </Response>
                     * </MFResponse>
                     */

                    if(type.equals("Upload")) {

                        String conflicts = eElement.getAttribute("conflicts");
                        int numConflicts = Integer.parseInt(conflicts.split("/")[0]);
                        int numUploads = Integer.parseInt(conflicts.split("/")[1]);

                        // In order of likelihood:

                        if(numConflicts == 0) {
                            JOptionPane.showMessageDialog(uploadPanel,"All uploads successful.", "Info", JOptionPane.INFORMATION_MESSAGE);
                        } else if(numConflicts < numUploads) {
                            JOptionPane.showMessageDialog(uploadPanel,"Some uploads failed. See Reports for details.", "Info", JOptionPane.INFORMATION_MESSAGE);
                        } else if(numConflicts == numUploads) {
                            JOptionPane.showMessageDialog(uploadPanel,"All uploads failed. See Reports for details.", "Info", JOptionPane.INFORMATION_MESSAGE);
                        }


                    } else if(type.equals("Error")) {

                        taskState = ERROR;

                        String errorCode = eElement.getAttribute("code");

                        if(errorCode.equals("MF_SESSION_EXPIRED")) {

                            JOptionPane.showMessageDialog(uploadPanel,"Upload Failed. Session has expired. Please login.", "Error", JOptionPane.ERROR_MESSAGE);

                            // even reading error response XML counts as data being read, so sees it as a normal task and updates progress bar

                            //uploadPanel.updateProgressBar(0);
                            //uploadPanel.uploadComplete();

                            //return null;
                            //setProgress(0);
                        }
                    }
                }

                //HttpEntity resEntity = response.getEntity();

                client.getConnectionManager().shutdown();

                //tmpXML.delete();

                ReportsPanel.log("response "+response);

            } catch( IOException e) {
                e.printStackTrace();
                System.err.println("Error: " + e.getMessage());
            }

            return null;
        }

        public void update(long l, long l1, int i) {

            uploadProgress = l;

            //setProgress((int) l);

            //ReportsPanel.log("PostData.UploadTask.update() "+l + " upload complete");

            uploadPanel.updateProgressBar(uploadProgress);

            // TODO rely on done() method being called when progress is complete (for XML response or actual working upload)
            // TODO see content lenghs are very different (XML 131, upload progress 3607)

            //if(uploadProgress == 100) {
            // uploadPanel.uploadComplete();
            //}
        }

        public void clearUploadStreams() {

            ReportsPanel.log("Postdata.clearUploadStreams()");

            ReportsPanel.log("Removing: " + tmpData.getAbsolutePath());
            tmpData.delete();   // client_tmpUploadXMLData.xml

            File tmpZip = new File("client_uploadfile.zip");
            ReportsPanel.log("Removing: " + tmpZip.getAbsolutePath());
            tmpZip.delete();    // client_uploadfile.zip

            try{
                httpPost.abort();
            } catch(Exception e) {

            }
            //Called by the garbage collector on an object when garbage collection determines that there are no more references to the object
            //httpPost.finalize();

            //client.getConnectionManager().shutdown();
        }

        public void done() {

            ReportsPanel.log("PostData.UploadTask.done() Task complete");

            switch(taskState) {

                case UPLOADING:
                    ReportsPanel.log("UPLOADING Done");
                    clearUploadStreams();
                    uploadPanel.uploadComplete();
                    break;
                case ERROR:
                    ReportsPanel.log("ERROR");
                    uploadPanel.uploadComplete();
                    uploadPanel.updateProgressBar(0);
                    break;
                default: ReportsPanel.log("Task.done() unknown taskState: "+ taskState);
            }

            taskState = COMPLETE;
        }

        //public void run() {
        //ReportsPanel.log("UploadTask.run() "+uploadProgress);
        //uploadPanel.updateProgressBar((int)uploadProgress);
        //}
    }


    class DownloadTask extends SwingWorker<Void, Void> {

        String postData;
        double bytesTotal;
        long progress;

        public DownloadTask(String postData){
            super();
            this.postData = postData;
        }
         // Main task. Executed in background thread.
        @Override
        public Void doInBackground() {

            taskState = DOWNLOAD;

            setProgress(0);

            ReportsPanel.log("DownloadTask.doInBackground()");

            try {
                url = new URL(mfResource);

                //if(conn == null ){
                    conn = (HttpURLConnection) url.openConnection(); // open connection, same as connection.connect()
                //}
                conn.setDoInput(true);   // specify that we will send output
                conn.setDoOutput(true);  // and accept input
                conn.setRequestMethod("POST");
                conn.setRequestProperty("Content-Type", "text/xml");  // tell the web server what we are sending
                conn.setUseCaches(false);
                conn.setConnectTimeout(20000);  // long timeout, but not infinite
                conn.setReadTimeout(20000);

                if(PostData.JSESSIONID_COOKIE_VALUE != null) {

                    ReportsPanel.log("setting JSESSIONID_COOKIE_VALUE "+PostData.JSESSIONID_COOKIE_VALUE);

                    ReportsPanel.log("*** Setting Request cookie "+PostData.JSESSIONID_COOKIE_VALUE);

                    conn.setRequestProperty("Cookie", "JSESSIONID="+JSESSIONID_COOKIE_VALUE +"; Path="+JSESSIONID_COOKIE_PATH);
                }

                try {
                    wr = new OutputStreamWriter(conn.getOutputStream());
                    wr.write(postData);
                    wr.flush();
                    wr.close();
                }catch (Exception e) {
                    System.err.println("Error: " + e.getMessage());
                }

                //InputStream inputStream = null;
                //InputStreamReader inputStreamReader = null
                //File outFile = null;
                //FileOutputStream fileOutputStream = null;

                contentLength = conn.getContentLength();

                ReportsPanel.log("DownloadTask.contentLength "+contentLength);

                if (contentLength != -1) {

                    Map map = conn.getHeaderFields();
                    Set set = map.entrySet();
                    Iterator iterator = set.iterator();

                    // **************** WOAHHHH WARNING ****************
                    // *** Having iterator.next() twice inside while ***
                    // *completely screwed download, no investigation *
                    // ended up in with server error: ClientAbortException:
                    // java.io.IOException
                    // **************** END WARNING ********************

                    while (iterator.hasNext()) {
                        ReportsPanel.log("Header-Field "+iterator.next());
                        //ReportsPanel.log(""+iterator.next());
                    }

                    String wholeCookie = conn.getHeaderField("Set-Cookie");

                    if(wholeCookie != null) {

                        ReportsPanel.log("PostData, parsing 'wholeCookie'");

                        PostData.JSESSIONID_COOKIE_VALUE = wholeCookie.split(";")[0].split("JSESSIONID=")[1];
                        PostData.JSESSIONID_COOKIE_PATH = wholeCookie.split(";")[1].split("Path=")[1];

                        ReportsPanel.log("*** Response PostData.JSESSIONID_COOKIE_VALUE "+PostData.JSESSIONID_COOKIE_VALUE);   // JSESSIONID=0AEA6305720CEC98D466849FAA4FBAC4; Path=/musicfinder

                        ReportsPanel.log("JSESSIONID_COOKIE_VALUE "+PostData.JSESSIONID_COOKIE_VALUE);

                        //conn.setRequestProperty("Set-Cookie", PostData.JSESSIONID_COOKIE);
                    }

                    /**
                     * For Data response (zip):
                     *  getHeaderFields null=[HTTP/1.1 200 OK]
                        getHeaderFields Date=[Sun, 03 Jun 2012 07:31:32 GMT]
                        getHeaderFields Content-Length=[3226400]
                        getHeaderFields Content-Disposition=[attachment; filename="/home/sam/mf_server_dump/music_finder_downloads/download.zip"]
                        getHeaderFields File-name=[outfile.zip]
                        getHeaderFields Uncompressed-File-size=[3312776]
                        getHeaderFields Compressed-File-size=[0]
                        getHeaderFields Content-Type=[text/html;charset=UTF-8]
                        getHeaderFields Server=[Apache-Coyote/1.1]
                        getHeaderFields Cache-Control=[no-cache]
                     */

                    /**
                     * For XML Session expired response (usual 131 length) and diff Content-Type:
                     *  getHeaderFields null=[HTTP/1.1 200 OK]
                        getHeaderFields Date=[Sun, 03 Jun 2012 07:36:00 GMT]
                        getHeaderFields Content-Length=[131]
                        getHeaderFields Content-Type=[application/xhtml+xml]
                        getHeaderFields Server=[Apache-Coyote/1.1]
                     */


                    inputStream = conn.getInputStream();

                    String contentType = conn.getHeaderField("Content-Type");

                    ReportsPanel.log("contentType " +contentType);
                    ReportsPanel.log("contentType equals" +contentType.equals("application/xhtml+xml"));

                    ReportsPanel.log("PostData.DownloadTask.contentType " +contentType);

                    boolean serverError = Boolean.parseBoolean(conn.getHeaderField("MFServer-Error"));

                    ReportsPanel.log("PostData.serverError " +serverError);

                    // TODO weak way to check for error
                    //if(contentType.equals("application/xhtml+xml")) { // this is blank if it is uploading / sending back progress (int, 1-100%)
                    ReportsPanel.log("serverError"+serverError);
                    if(serverError) {


                        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);

                        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
                        DocumentBuilder dBuilder = null;

                        try {
                            dBuilder = dbFactory.newDocumentBuilder();
                            doc = dBuilder.parse(new InputSource(inputStreamReader));
                        } catch (ParserConfigurationException e) {
                            ReportsPanel.log(e.toString());
                            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                        } catch (SAXException e) {
                            ReportsPanel.log(e.toString());
                                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                            }

                        NodeList nList = doc.getElementsByTagName("Response");
                        Node nNode = nList.item(0);
                        Element eElement = (Element) nNode;
                        String type = eElement.getAttribute("type");

                        /**
                             * <MFResponse>
                             *     <Response type='Error' code='MF_SESSION_EXPIRED'>
                             *         </Response>
                             * </MFResponse>
                         *
                         * or
                         *
                                <MFResponse>
                                    <Response type='Error' code='ALL_DOWNLOADS_404'>
                                    </Response>
                                </MFResponse>

                             */

                    // TODO Cos XML response is sent, we still hit "PostData.DownloadTask.done() Task complete"
                    // so Streams/Connections should be cleaned up ?
                    // TODO get ZipFile or zipInputStream error: java.util.zip.ZipException: error in opening zip file
                    // when trying new download after error from server

                        if(type.equals("Error")) {  // TODO remove, already checked for error header

                                taskState = ERROR;
                                //clearDownloadStreams();
                                //resultsPanel.downloadComplete();

                                String errorCode = eElement.getAttribute("code");

                                ReportsPanel.log("errorCode "+errorCode);

                                if(errorCode.equals("MF_SESSION_EXPIRED")) {
                                   JOptionPane.showMessageDialog(uploadPanel,"Download Failed. Session has expired. Please login.", "Error", JOptionPane.ERROR_MESSAGE);
                                }
                                else if (errorCode.equals("ALL_DOWNLOADS_404")) {
                                       JOptionPane.showMessageDialog(uploadPanel,"Download Failed. \nSelected tracks were not found in repository", "Error", JOptionPane.ERROR_MESSAGE);
                                }
                            }

                    } else {

                    // number of bytes you can be guaranteed to read (without blocking) the next time you call read()
                    // available() 8730 for 130 kb (bits read = 133641, kilobits 130.5087890625) example.
                    //ReportsPanel.log("available() "+ inputStream.available());
                    //ReportsPanel.log("conn.getContentLength() "+contentLength);

                    // TODO why do we send this from server?
                    String outFileName = conn.getHeaderField("File-name");  // outfile.zip

                    ReportsPanel.log("String outFileName "+outFileName);

                    //int outFileSize = Integer.parseInt(conn.getHeaderField("File-size"));
                    //int outFileSize = Integer.parseInt(conn.getHeaderField("File-size"));
                    totalCompressedSize = Integer.parseInt(conn.getHeaderField("Compressed-File-size"));
                    totalUncompressedSize = Integer.parseInt(conn.getHeaderField("Uncompressed-File-size"));

                        ReportsPanel.log("totalCompressedSize "+totalCompressedSize);
                        ReportsPanel.log("totalUncompressedSize "+totalUncompressedSize);

                    outFileSize = totalUncompressedSize;

                    // downloadDirName = "/home/sam/mf_server_dump/music_finder_downloads";
                    downloadDirName = SettingsPanel.USER_SETTING_DOWNLOAD_DIR;


                    //File downloadDir = new File(downloadDirName);
                    //ReportsPanel.log("downloadDir.exists() "+downloadDir.exists());
                    //ReportsPanel.log("downloadDir.getAbsolutePath() "+downloadDir.getAbsolutePath());
                    //ReportsPanel.log("downloadDir.getPath() "+downloadDir.getPath());
                    //if(!downloadDir.exists()) {
                    //downloadDir.mkdir();
                        //ReportsPanel.log("download directory created: " + success);
                    //}
                    //ZipFile ze = new ZipFile(downloadDirName+"/"+outFileName);
                    //outFile = new File(downloadDirName, ze.getName());

                    /**
                     * java.io.IOException: Not a directory
                        at java.io.UnixFileSystem.createFileExclusively(Native Method)
                        at java.io.File.createNewFile(File.java:883)

                     http://stackoverflow.com/questions/7469018/cannot-make-file-java-io-ioexception-no-such-file-or-directory
                     */
                    //outFile = new File(downloadDirName+File.separator+outFileName);
                    //outFile = outFile.getAbsoluteFile();
                    //ReportsPanel.log("outFile.getAbsolutePath() "+outFile.getAbsolutePath());
                    //boolean success = outFile.createNewFile();  // IMPORTANT: appeared to not cope with lots of files, threw ZipFile IO Exception
                    //ReportsPanel.log("zip file created: " + success);
                    //outFile = new ZipFile(new File(downloadDirName+File.separator+outFileName));


                    // User defined location + outFile = tmp zip file containing all tracks
                    ReportsPanel.log("Download location: "+outFile);

                        //WARNING very important!!!!!!!!!!!!!!!!!
                        // we have already created/set outFile as a new File, so we dont need:
                        //downloadDirName+File.separator+outFile otherwise we end up with duplicate paths:
                        // /home/sam/IdeaProjects/downloads//home/sam/IdeaProjects/downloads/outfile.zip

                    fileOutputStream = new FileOutputStream(outFile);

                    ReportsPanel.log("fileOutputStream: " + fileOutputStream);
                    ReportsPanel.log("inputStream: " + inputStream);

                    //inputStream = conn.getInputStream();

                    //fileOutputStream = new FileOutputStream(new File(downloadDirName+"/"+outFile.getName()));
                    //fileOutputStream = new FileOutputStream(new File(downloadDirName+"/"+zipFile.getName()));
                    //fileOutputStream = new FileOutputStream(outFile);
                    //inputStreamReader = new InputStreamReader(inputStream);

                    int BYTES_DOWNLOAD = 1024;
                    int read=0;
                    bytesTotal = 0;
                    byte[] bytes = new byte[BYTES_DOWNLOAD];

                    resultsPanel.updateProgressBar(0);

                    setProgress(0);     // Sets the progress bound property. The value should be from 0 to 100

                    /**
                                  * int num = 5;
                                  * int denom = 7;
                                  * double d = num / denom;   >>>> will result in 0 in Java.
                                  * so both bytesTotal & contentLength must 'double' type
                                  * and the result must be double:
                                  */

                    //taskState = DOWNLOAD;

                    resultsPanel.setProgressBarStr("Downloading...");

                    while((read = inputStream.read(bytes)) != -1) {

                        fileOutputStream.write(bytes, 0, read);
                        bytesTotal+= read;
                        double tmpLong = bytesTotal / contentLength;
                        progress = (long) (tmpLong * 100);    // e.g. (15092736 / 29037252)* 100 = 52%
                        setProgress((int)progress);
                    }

                    // SERVER: servletOutputStream.bytes written: 32263111
                    // CLIENT: bytesTotal 3.2263111E7

                    ReportsPanel.log(progress + "%");
                    ReportsPanel.log("bytesTotal "+bytesTotal);
                    ReportsPanel.log("Download complete Download location: "+outFile);
                }
            }   // end "elseclearUploadStreams" for Content-Type check
            } catch (FileNotFoundException e) {
                    ReportsPanel.log(e.toString());
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            } catch (IOException e) {
                    ReportsPanel.log(e.toString());
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }

            return null;
        }

        public void clearDownloadStreams() {

            ReportsPanel.log("clearDownloadStreams()");

            try {
                if(fileOutputStream != null ){
                    fileOutputStream.flush();
                    fileOutputStream.close();
                }

                if(inputStream != null ){
                    inputStream.close();
                }

                if(conn != null ){
                    conn.disconnect();
                }

                //System.runFinalization();
                //System.gc();
            }catch (FileNotFoundException e) {
                    ReportsPanel.log(e.toString());
                    e.printStackTrace();
            } catch (IOException e) {
                    ReportsPanel.log(e.toString());
                    e.printStackTrace();
            }
        }

        // When the background task is complete,  instance invokes  done method in the event-dispatching thread
        public void done() {

            ReportsPanel.log("PostData.DownloadTask.done() Task complete");

            switch(taskState) {

                case DOWNLOAD:
                    ReportsPanel.log("DOWNLOAD");

                    clearDownloadStreams();
                    // do we want to keep download compressed
                    if(SettingsPanel.USER_SETTING_DECOMPRESS.equals("no")) {
                        uncompressTask.execute();
                    } else {
                        // dont need to clear zip streams as not used yet
                        // also dont want to delete outfile.zip
                        // so just say we have finished
                        resultsPanel.downloadComplete();
                        resultsPanel.openDownloads(outFile);
                    }

                    break;
                case UNCOMPRESSING:   // TODO no point - each done() in its own ActionWorker, so cannot be called for different states, only current state Actionworker is dealing with
                    //ReportsPanel.log("UNCOMPRESSING");
                    //clearZipStreams();
                    //resultsPanel.downloadComplete();
                    break;
                case ERROR:
                      ReportsPanel.log("ERROR");
                      clearDownloadStreams();     // TODO havent actually used these yet... what about 2nd time around if error?
                      resultsPanel.downloadComplete();
                      break;
                default:
                    ReportsPanel.log("Task.done() unknown taskState: "+ taskState);
                    //clearDownloadStreams();
                    //resultsPanel.downloadComplete();
            }

            taskState = COMPLETE;
        }
    }

    class UncompressTask extends SwingWorker<Void, Void> {

        String postData;
        double bytesTotal;
        long progress;

        public UncompressTask(String postData){
            super();
            this.postData = postData;
        }

        @Override
        public Void doInBackground() {

            //setProgress(0);

            ReportsPanel.log("UncompressTask.doInBackground()");

            //ReportsPanel.log(bytesTotal + " 'v' " + outFileSize);

                /**
                             *   Could compare bytes read per file with total bytes per file.
                             *   Create new FileOutputStream each time they are equal.
                             *   Only close streams & connections when total bytes read for all files is reached (in headers from server).
                             *   File names could be listed in header, or taken from req XML.
                             */

                //if(outFileSize == bytesTotal){
                    // ******************************************************************
                    // Although progress() is now 100 (%), we havent returned null
                    // so Task.done() isn't executed. So manually clear streams:
                    //clearDownloadStreams();
                    // ******************************************************************
                //}

                //ReportsPanel.log("TEMP NEW FILE SIZE: " + new File(downloadDirName+"/"+outFile.getName()).length());

                //ZipFile zipFile = null;
                //ZipInputStream zipInputStream = null;
                //Enumeration entries = null;

                Vector <ZipEntry> entries;
                entries = new Vector<ZipEntry>();

                //ReportsPanel.log("Get zip file: " +downloadDirName+"/"+outFile.getName());    // music finder downloads/outfile.zip

                // file should already been created (downloaded above?)

                // stackoverflow.com/questions/325202/java-util-zip-zipexception-error-in-opening-zip-file
                // http://java.sun.com/developer/technicalArticles/Programming/compression/
                // Not using a BufferedInputStream sppears to solve java.util.zip.ZipException: error in opening zip file
                // exception when downloading / extracting lots of files????????

                try {

                    //zipFile = new ZipFile(outFile.getAbsolutePath());

                    //File tmpFile = new File(downloadDirName+File.separator+outFile.getName());
                    //ReportsPanel.log("ZIP FILE PATH "+tmpFile.getAbsolutePath());
                    //ReportsPanel.log("ZIP FILE EXISTS "+tmpFile.exists());

                    //zipInputStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(tmpFile.getAbsolutePath())));

                    ReportsPanel.log("Extracting downloaded file: "+downloadDirName+File.separator+outFile.getName());

                    zipFile = new ZipFile(downloadDirName+File.separator+outFile.getName());
                    zipInputStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipFile.getName())));

                    //zipInputStream = new ZipInputStream(new FileInputStream(zipFile.getName()));

                    Enumeration tmpEnum = zipFile.entries();

                    while(tmpEnum.hasMoreElements()) {
                        ZipEntry ze = (ZipEntry) tmpEnum.nextElement();
                        //contentLength += ze.getSize();
                        //contentLength += ze.getCompressedSize();
                        entries.add(ze);
                    }

                } catch (Exception e) {
                    ReportsPanel.log("ZipFile or zipInputStream error: " + e.toString());

                    e.printStackTrace();
                }

                ReportsPanel.log("Extracting compressed data");

                bytesTotal = 0;
                setProgress(0);
                // contentLength = zipFile.size(); // wrong, this gets number of zip entries in zip file, not byte size

                ReportsPanel.log("contentLength "+contentLength);
                ReportsPanel.log("bytesTotal "+bytesTotal);

                Iterator zipIt = entries.iterator();

                // WARNING overriding contentLength set above
                //contentLength = contentLength/2;
                //contentLength = outFileSize;

                // contentLength = accumulative of each zip entry = bytes read never reach target (target is double that of bytes read, 3312776.0 'v' 6452628.0 = 51%).
                // contentLength = file length() of total zip file = bytes read go over (just) the target (target is slightly too high 3258630.0 'v' 3226400.0 = 100%).

                taskState = UNCOMPRESSING;
                resultsPanel.setProgressBarStr("Uncompressing...");

                // reading of zip inputstream (uncompressing data), is more than 'target'
                // of zip file number of bytes  (from zip file length()) 3258630.0 'v' 3226382.0

                ReportsPanel.log("entries "+entries.size());

                //int numEntries = entries.size();

                int BYTES_DOWNLOAD = 1024;
                int read=0;
                bytesTotal = 0;         // do not reset, otherwise we end up with bytesTotal divided by num entries!!! 4 tracks = 25%, instead of 100%

                do{

                    ZipEntry entry = (ZipEntry) zipIt.next();

                    ReportsPanel.log("ZipEntry "+entry);

                    try {
                        out = new FileOutputStream(downloadDirName+File.separator+entry.getName());
                        zipEntryInputStream = zipFile.getInputStream(entry);
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    byte[] bytes = new byte[BYTES_DOWNLOAD];

                    try {
                        while((read = zipEntryInputStream.read(bytes)) != -1) {

                            out.write(bytes, 0, read);
                            bytesTotal+= read;
                            double tmpLong = bytesTotal / contentLength;
                            progress = (long) (tmpLong * 100);    // e.g. (15092736 / 29037252)* 100 = 52%
                            setProgress((int)progress);

                            //ReportsPanel.log(bytes + " read: " +bytesTotal + " targ: " + contentLength + " " + progress + "%");
                        }
                    } catch (IOException e) {
                        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    }

                    ReportsPanel.log("bytesTotal >= contentLength "+(bytesTotal >= contentLength));

                    /*if(bytesTotal >= contentLength){
                                    ReportsPanel.log("Closing zipEntryInputStream");
                                    zipEntryInputStream.close();
                                    ReportsPanel.log("Closing zip entry FileOutputStream");
                                    out.close();
                                }*/

                    //numEntries--;

                }while(zipIt.hasNext());

                //ReportsPanel.log("Closing main zip FileOutputStream (no more entries)");
                //zipInputStream.close();

                //success = outFile.delete();

                //ReportsPanel.log("Removed zip file: "+success);

            return null;
        }

        // When the background task is complete,  instance invokes  done method in the event-dispatching thread
        public void done() {

            ReportsPanel.log("PostData.done() Task complete");

            switch(taskState) {

                case DOWNLOAD:
                    //ReportsPanel.log("DOWNLOAD done");
                    //clearDownloadStreams();
                    break;
                case UNCOMPRESSING:
                    ReportsPanel.log("UNCOMPRESSING done");
                    clearZipStreams();
                    resultsPanel.downloadComplete();
                    resultsPanel.openDownloads(outFile);
                    break;
               case UPLOADING:
                    ReportsPanel.log("UPLOADING done");
                    break;
                default: ReportsPanel.log("Task.done() unknown taskState: "+ taskState);
            }

            taskState = COMPLETE;
        }

        public void clearZipStreams() {

            ReportsPanel.log("clearZipStreams()");

            try {
                ReportsPanel.log("Closing zipEntryInputStream");
                zipEntryInputStream.close();

                ReportsPanel.log("Closing zip entry FileOutputStream");
                out.close();

                ReportsPanel.log("Closing main zip FileOutputStream (no more entries)");
                zipInputStream.close();

                ReportsPanel.log("Removing downloaded zip files "+downloadDirName+"outfile.zip");

                //File outFile = new File("music_finder_downloads", "outfile.zip");

                // downloadDirName+File.separator+"outfile.zip"
                File outFile = new File(downloadDirName+File.separator, "outfile.zip");
                //ReportsPanel.log("Deleting downloaded zip files "+outFile.getAbsolutePath());

                boolean success = outFile.delete();

                ReportsPanel.log("Removed zip file: "+success);

            }catch (FileNotFoundException e) {
                    ReportsPanel.log(e.toString());
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            } catch (IOException e) {
                    ReportsPanel.log(e.toString());
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
        }


    }

    /**
     * @param postData
     * <MFRequest>
     *     <Request type='Upload'/>
     *     <Uploads>
     *         <Track file='Compuphonic - Sunset.mp3' name='Sunset' artist='Compuphonic' genre='Dance' type='.mp3'/>
     *     </Uploads>
     * </MFRequest>

     * @param uploadFile
     * client_uploadfile.zip    // stores actual mp3s
     */

    public void uploadFiles(String postData, String uploadFile, UploadPanel uploadPanel) {

        ReportsPanel.log("PostData.uploadFiles()");

        File tmpUpload = new File(uploadFile);

        File tmpXML = new File("client_tmpUploadXMLData.xml");

        tmpXML.setReadable(true);
        tmpXML.setWritable(true);

        taskState = UPLOADING;

        uploadTask = new UploadTask(postData, uploadFile, uploadPanel, tmpUpload, tmpXML);

        uploadTask.execute();

        /**
         * You need to set cookies (request header values) before you write to the request body.
         * You can't change the request headers anymore when the first bit of the request body is been sent.
         *

If you retrieve the URLConnection object using openConnection() it doesn't actually start communicating with the server. That doesn't happen until you get the stream from the URLConnection(). When you first get the connection you can add/change headers and other connection properties before actually opening it.

URLConnection's lifecycle is a bit odd. It doesn't send the headers to the server until you've gotten one of the streams. If you just get the input stream then I believe it does a GET, sends the headers, then lets you read the output. If you get the output stream then I believe it sends it as a POST, as it assumes you'll be writing data to it (You may need to call setDoOutput(true) for the output stream to work).

         As soon as you get the input stream the output stream is closed and it waits for the response from the server.
         */

        /**
         * If we are using normal html file for upload no issue, browser takes care of "boundary"...

but if we write core java app to connect to filedownload app, we need to set "boundary" manually...
I have set boundary manually,it works.But it does not work without boundary.

is it possible to download files without boundary?
is there any other api which can do it?

if I remove boundary from code it gives me error:
org.apache.commons.fileupload.FileUploadException: the request was rejected because no multipart boundary was found

         INNER CLASS / THREAD SCOPE?

         this needs to be inner class / background thread anyway for progress bar. but this could be why url / conn objs
         appear reusable when run in Task classes.
         */

        /**
         * Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
	at org.apache.http.impl.client.AbstractHttpClient.<init>(AbstractHttpClient.java:182)
	at org.apache.http.impl.client.DefaultHttpClient.<init>(DefaultHttpClient.java:150)
	at client.comms.PostData.uploadFiles(PostData.java:702)

         */
    }
}










// Reader bufferedReader = new BufferedReader(inputStreamReader);

                //while((read = bufferedReader.read()) != -1){
                    //ReportsPanel.log("read " + read);
                    //fileOutputStream.write(bytes, 0, read);
                //}

                //while((read = inputStreamReader.read()) != -1){
                    //ReportsPanel.log("read " + read);
                    //fileOutputStream.write(bytes, 0, read);
                //}

/*
            url = new URL(mfResource);

            ReportsPanel.log(conn);//.disconnect();

            // URL openStream() returns InputStream, but we want to write, so use:
            conn = (HttpURLConnection) url.openConnection(); // open connection, same as connection.connect()
            conn.disconnect();
            //conn.setDoInput(true);
            conn.setDoOutput(true);
            conn.setRequestMethod("POST");
            // multipart/mixed ?
            //;boundary=---------------------------sdjkahdj294919323195"
            conn.setRequestProperty("Content-Type", "multipart/form-data");  // tell the web server what we are sending
            conn.setUseCaches(false);
            conn.setConnectTimeout(20000);  // long timeout, but not infinite
            conn.setReadTimeout(20000);

            conn.connect();


            // The content type "multipart/form-data" should be used for submitting forms that contain files, non-ASCII data, and binary data.
            // The content "multipart/form-data" follows the rules of all multipart MIME data streams.
            // As with all multipart MIME types, each part has an optional "Content-Type"

            //conn.setRequestProperty("Content-Type", "multipart");
            //conn.setRequestProperty("content-type", "multipart");

            //ReportsPanel.log("conn.getContentType() " + conn.getContentType());
            //ReportsPanel.log("conn.getRequestProperty() "+conn.getRequestProperty("Content-Type"));

            //conn.setUseCaches(false);
            //conn.setConnectTimeout(20000);  // long timeout, but not infinite
            //conn.setReadTimeout(20000);



            OutputStream outputStream = null;
            BufferedOutputStream bufferedOutputStream = null;
            FileInputStream fileInputStream = null;

            int BYTES_DOWNLOAD = 1024;
            int read=0;
            int bytesTotal = 0;
            byte[] bytes = new byte[BYTES_DOWNLOAD];

            try {

                outputStream = conn.getOutputStream();
                bufferedOutputStream = new BufferedOutputStream(outputStream);

                File f = new File(uploadFile);

                fileInputStream = new FileInputStream(f);

                while((read = fileInputStream.read(bytes)) != -1)
                {
                    //bufferedOutputStream.write(bytes, 0, read);
                    bufferedOutputStream.write(bytes);
                    bytesTotal += read; // content length should be this?
                    //outputStream.write(read);
                    //outputStream.write(bytes, 0, read);
                }

                ReportsPanel.log("bytesTotal "+bytesTotal);
                //wr = new OutputStreamWriter(conn.getOutputStream());
                //wr.write(postData);
                bufferedOutputStream.flush();
                bufferedOutputStream.close();
            }catch (Exception e1) {
                e1.printStackTrace();
                System.err.println("Error: " + e1.getMessage());
            }
        } catch( MalformedURLException e2) {
            e2.printStackTrace();
            System.err.println("Error: " + e2.getMessage());
        } catch( ProtocolException e3) {
            e3.printStackTrace();
            System.err.println("Error: " + e3.getMessage());
        } catch( IOException e4) {
            e4.printStackTrace();
            System.err.println("Error: " + e4.getMessage());
        } */


    /**
     *  public void propertyChange(PropertyChangeEvent evt) {
    if ("progress" == evt.getPropertyName()) {
      int progress = (Integer) evt.getNewValue();
      progressMonitor.setProgress(progress);
      String message = String.format("Completed %d%%.\n", progress);
      progressMonitor.setNote(message);
      taskOutput.append(message);
      if (progressMonitor.isCanceled() || task.isDone()) {
        Toolkit.getDefaultToolkit().beep();
        if (progressMonitor.isCanceled()) {
          task.cancel(true);
          taskOutput.append("Task canceled.\n");
        } else {
          taskOutput.append("Task completed.\n");
        }
        startButton.setEnabled(true);
      }
    }
  }
     */


    /**
     * @Override
        public Void doInBackground() {
            Random random = new Random();
            int progress = 0;
            //Initialize progress property.
            setProgress(0);
            //Sleep for at least one second to simulate "startup".
            try {
                Thread.sleep(1000 + random.nextInt(2000));
            } catch (InterruptedException ignore) {}
            while (progress < 100) {
                //Sleep for up to one second.
                try {
                    Thread.sleep(random.nextInt(1000));
                } catch (InterruptedException ignore) {}
                //Make random progress.
                progress += random.nextInt(10);
                setProgress(Math.min(progress, 100));
            }
            return null;

     @param evt
    public void actionPerformed(ActionEvent evt) {  // Invoked when the user presses the start button.
        progressBar.setIndeterminate(true);
        startButton.setEnabled(false);
        Instances of javax.swing.SwingWorker are not reusuable, so
        we create new instances as needed.
        Task task = new Task();
        task.addPropertyChangeListener(this);
        task.execute();
    }

    public void propertyChange(PropertyChangeEvent evt) {
    if (!done) {
       int progress = task.getProgress();
       progressBar.setValue(progress);
        taskOutput.append(String.format(
               "Completed %d%% of task.\n", progress));
    }
    }

    // When the background task is complete, the instance invokes the done method in the event-dispatching thread.
    //Tell progress listener to stop updating progress bar
    public void done() {
    done = true;
    Toolkit.getDefaultToolkit().beep();
    startButton.setEnabled(true);
    setCursor(null); //turn off the wait cursor
    progressBar.setValue(progressBar.getMinimum());
    taskOutput.append("Done!\n");
    }
     */